Rmalliu/feat/multi share mps - #12
Draft
radu-malliu wants to merge 3 commits into
Draft
Conversation
Honor FailRequestsGreaterThanOne in the MPS sharing strategy the same way it is honored for time-slicing. Previously this flag was forcibly set to true and then ignored, making it impossible to request more than one shared GPU unit under MPS. Now the flag defaults to false, allowing multi-unit MPS requests unless explicitly restricted. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
storage.googleapis.com/golang now returns 403 for golang tarballs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…licas Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Summary
Enable pods on MPS-enabled GPU clusters to request more than one shared replica and, when they request all replicas, get the full GPU hardware. Two coordinated changes in this PR:
failRequestsGreaterThanOneauthoritative for MPS (commit793753a1). Today the flag is overridden totrueand then ignored, so MPS resource requests > 1 are always rejected regardless of config. This change honors the configured value the same way time-slicing already does. The default remainstrue(reject multi-replica requests) for backward compatibility — clusters opt in by explicitly settingsharing.mps.failRequestsGreaterThanOne: falsein the device-plugin config.fcdd6c77). MPS control daemon's server-side caps are raised to full per-device memory and 100% threads, and theAllocate()response injectsCUDA_MPS_ACTIVE_THREAD_PERCENTAGEandCUDA_MPS_PINNED_DEVICE_MEM_LIMITat1/replicasfor any non-full-node grant — preserving today's behavior for partial requests, while letting all-replica grants ride unrestricted daemon defaults.Motivation
The MPS control daemon sets per-client per-device caps at startup:
set_default_device_pinned_mem_limit <index> <totalMem/replicas>set_default_active_thread_percentage <100/replicas>Per NVIDIA's MPS docs (appendix on
CUDA_MPS_PINNED_DEVICE_MEM_LIMIT):So the daemon default is a hard cap — client env vars can request less, never more. The result: even a container that's been allocated every replica ID on a node is still throttled to
1/replicasper physical GPU.nvidia-smicorrectly shows all GPUs because the device nodes are mounted; the MPS serversilently caps allocations.
How it works
Two coordinated touchpoints:
1.
cmd/mps-control-daemon/mps/daemon.go— daemon defaults raised:perDevicePinnedDeviceMemoryLimits()→ fulldevice.TotalMemoryper physical indexactiveThreadPercentage()→100These were the floor for every client; raising them lets the client env vars (which can only clamp down, never up) become the effective cap.
2.
internal/plugin/server.go+internal/plugin/mps.go— Allocate response:updateResponseForMPSandmpsOptions.updateReponsenow takegrantedCount int(=len(req.DevicesIDs)).grantedCount >= total advertised replicas, no env vars are injected; the container inherits the daemon's full-hardware defaults.CUDA_MPS_ACTIVE_THREAD_PERCENTAGE = 100/replicasCUDA_MPS_PINNED_DEVICE_MEM_LIMIT = "<idx>=<totalMem/replicas>M,<idx>=<totalMem/replicas>M,..."Both per-physical-GPU values come from
m.daemon.Devices()— already in scope, no new plumbing of device IDs needed.Why this is safe (no oversubscription)
The full-grant branch (
grantedCount >= total) only triggers when kubelet's device-plugin accounting has handed this single container every replica ID the plugin advertises. Kubelet's accounting guarantees:So at the moment unrestricted defaults apply, exactly one MPS client exists on the node. NVIDIA's documented hard cap of 60 client CUDA contexts per device (Volta+) is never the active constraint — kubelet refuses oversubscription long before MPS would.
The "mixed allocation" footgun (a pod gets all replicas of GPU-A + some of GPU-B on a multi-GPU node) cannot satisfy
grantedCount == totalby construction; it's all-or-nothing.Operational notes
set_default_*is applied once at MPS daemon startup; existing clients keep their old caps. Rolling the daemonset disrupts in-flight MPS clients — coordinate with workloads./dev/shmbind mount into every MPS client. cwtest's per-node test pods currently also create anemptyDir{medium:Memory}at/dev/shm, stacking the two and triggering a gamble[7D07] sys_disk_mountsfalse negative. This PR doesn't touch that —follow-up changes in
coreweave/getnodes(skip the emptyDir on MPS nodes) andcoreweave/gamble(env-var-aware/dev/shmsource check) are needed to keep HPC-V passing.Daemon.Start()).What this does NOT cover
1/replicasper-device cap as before, regardless of how many replicas it owns of any specific physical GPU. The framework's there if it's ever wanted (groupreq.DevicesIDsby UUID andcount per-physical), but not needed for the current goal.
set_active_thread_percentage <PID>via the control daemon (alternative to env vars). More complex (host PID plumbing, fork/exec doesn't propagate per-PID settings) and not needed for the all-or-nothing case.internal/plugin/mps.go.Test plan
go build ./...go test ./cmd/mps-control-daemon/... ./internal/plugin/...mps-lab(g884dd0, 8× RTX PRO 6000 Blackwell, MPS replicas=10).nvidia-device-plugin-mps-control-daemondaemonset; confirm logs showset_default_active_thread_percentage 100andset_default_device_pinned_mem_limit <idx> <fullMem>Mfor each GPU.nvidia.com/gpu: "10"(one full physical GPU's worth of replicas). Inside the pod:CUDA_MPS_ACTIVE_THREAD_PERCENTAGEandCUDA_MPS_PINNED_DEVICE_MEM_LIMITenv vars are not set (full-node-per-GPU branch path), OR the equivalent 100% / full-memory values depending on how the resource was allocated.totalMem/10pinned memory on the assigned GPU; confirm it succeeds (previously wouldcudaErrorOutOfMemory).nvidia.com/gpu: "1"(partial). Confirm:CUDA_MPS_ACTIVE_THREAD_PERCENTAGE=10is set.CUDA_MPS_PINNED_DEVICE_MEM_LIMITcontains entries for each physical GPU attotalMem/10M.totalMem/10fails withcudaErrorOutOfMemory— i.e. the cap is still enforced for partial grants.nvidia.com/gpu: "80"for the lab node). Confirm no env vars injected and CUDA can use the full hardware on every GPU.References
CUDA_MPS_PINNED_DEVICE_MEM_LIMIT/CUDA_MPS_ACTIVE_THREAD_PERCENTAGEsemantics: https://docs.nvidia.com/deploy/mps/appendix-tools-and-interface-reference.html🤖 Generated with Claude Code